﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;

namespace Djs.Common.Components
{
    /// <summary>
    /// Splitter (Horizontal, Vertical)
    /// </summary>
    public class GSplitter : InteractiveItem, IInteractiveItem
    {
        #region Public property and events
        /// <summary>
        /// Visible bounds of splitter.
        /// </summary>
        public Rectangle Bounds { get { return this._Bounds; } set { this._CalcBounds(value); } }
        /// <summary>
        /// Orientation of splitter.
        /// </summary>
        public Orientation Orientation { get { return this._Orientation; } set { this._Orientation = value; this._CalcActiveBounds(); } }
        /// <summary>
        /// Logical location of splitter. In coordinates of control. Represent a center of Bounds on axis by Orientation.
        /// </summary>
        public int Location { get { return this._Location; } set { this._Location = value; this._CalcBounds(); } }
        /// <summary>
        /// Minimal value of Location (null (default) = without limit
        /// </summary>
        public int? LocationMin { get { return this._LocationMin; } set { this._LocationMin = value; this._CalcBounds(); } } 
        /// <summary>
        /// Maximal value of Location (null (default) = without limit
        /// </summary>
        public int? LocationMax { get { return this._LocationMax; } set { this._LocationMax = value; this._CalcBounds(); } }
        /// <summary>
        /// Visible width of splitter.
        /// </summary>
        public int Width { get { return this._Width; } set { this._Width = value; this._CalcBounds(); } }
        /// <summary>
        /// Overhead of active zone of splitter (splitter is active in neighborough area).
        /// </summary>
        public int ActiveOverhead { get { return this._ActiveOverhead; } set { this._ActiveOverhead = value; this._CalcActiveBounds(); } }
        /// <summary>
        /// Event triggered on every step on location change (= during MouseDragMove)
        /// </summary>
        public event EventHandler LocationChanging;
        /// <summary>
        /// Event triggered after location change is done (= on MouseDragEnd)
        /// </summary>
        public event EventHandler LocationChanged;
        /// <summary>
        /// Calculate Bounds from Bounds (in inactive direction), Loacation, Orientation and Width
        /// </summary>
        private void _CalcBounds()
        {
            this._CalcBounds(this._Bounds);
        }
        /// <summary>
        /// Calculate Bounds from Bounds (in inactive direction), Loacation, Orientation and Width.
        /// Does not change Location !!!
        /// </summary>
        private void _CalcBounds(Rectangle bounds)
        {
            Rectangle r = bounds;
            int loc = this._Location;
            int wid = this._Width;
            int wd1 = wid / 2;
            int wd2 = wid - wd1;
            switch (this._Orientation)
            {
                case System.Windows.Forms.Orientation.Horizontal:
                    this._Bounds = new Rectangle(r.X, loc - wd1, r.Width, wd2);
                    break;
                case System.Windows.Forms.Orientation.Vertical:
                    this._Bounds = new Rectangle(loc - wd1, r.Y, wd2, r.Height);
                    break;
                default:
                    this._Bounds = r;
                    break;
            }
            this._CalcActiveBounds();
        }
        /// <summary>
        /// Calculate Location and Width from Bounds and Orientation
        /// </summary>
        private void _CalcLocation()
        {
            Rectangle r = this._Bounds;
            switch (this._Orientation)
            {
                case System.Windows.Forms.Orientation.Horizontal:
                    this._Location = r.Y + (r.Height / 2);
                    this._Width = r.Height;
                    break;
                case System.Windows.Forms.Orientation.Vertical:
                    this._Location = r.X + (r.Width / 2);
                    this._Width = r.Width;
                    break;
            }
        }
        /// <summary>
        /// Calculate ActiveBounds from Bounds, Orientation and ActiveOverhead
        /// </summary>
        private void _CalcActiveBounds()
        {
            Rectangle r = this._Bounds;
            int o = this._ActiveOverhead;
            switch (this._Orientation)
            {
                case System.Windows.Forms.Orientation.Horizontal:
                    this.ActiveBounds = new Rectangle(r.X, r.Y - o, r.Width, r.Height + 2 * o);
                    break;
                case System.Windows.Forms.Orientation.Vertical:
                    this.ActiveBounds = new Rectangle(r.X - o, r.Y, r.Width + 2 * o, r.Height);
                    break;
                default:
                    this.ActiveBounds = r;
                    break;
            }
        }
        /// <summary>
        /// Contain value of location, aligned between (LocationMin ÷ LocationMax), include.
        /// </summary>
        private int _Location
        {
            get { return this._GetLocationAligned(this.__Location); }
            set { this.__Location = value; }
        }
        /// <summary>
        /// Return a location aligned between (LocationMin ÷ LocationMax), include.
        /// </summary>
        /// <param name="location"></param>
        /// <returns></returns>
        private int _GetLocationAligned(int location)
        {
            int result = location;
            if (this._LocationMax.HasValue && result > this._LocationMax.Value) result = this._LocationMax.Value;
            if (this._LocationMin.HasValue && result < this._LocationMin.Value) result = this._LocationMin.Value;
            return result;
        }
        private Rectangle _Bounds;
        private Orientation _Orientation = Orientation.Horizontal;
        private int __Location;
        private int? _LocationMin;
        private int? _LocationMax;
        private int _Width = 4;
        private int _ActiveOverhead;
        private bool _IsHorizontal { get { return (this._Orientation == System.Windows.Forms.Orientation.Horizontal); } }
        private bool _IsVertical { get { return (this._Orientation == System.Windows.Forms.Orientation.Vertical); } }
        #endregion
        #region Interactivity
        protected override void StateChanged(GInteractiveChangeStateArgs e)
        {
            switch (e.ChangeState)
            {
                case GInteractiveChangeState.MouseEnter:
                    e.RequiredCursorType = (_IsHorizontal ? SysCursorType.HSplit : SysCursorType.VSplit);
                    this.RepaintToLayers = GInteractiveDrawLayer.Standard;
                    break;
                case GInteractiveChangeState.LeftDragBegin:
                    e.UserDragPoint = new Point(this.Location, this.Location);
                    this.RepaintToLayers = GInteractiveDrawLayer.Standard;
                    this.RepaintToLayers = GInteractiveDrawLayer.Interactive;
                    break;
                case GInteractiveChangeState.LeftDragMove:
                    if (e.UserDragPoint.HasValue)
                    {
                        this.Location = (this._IsHorizontal ? e.UserDragPoint.Value.Y : e.UserDragPoint.Value.X);
                        this._OnLocationChanging(e);
                        this.RepaintToLayers = GInteractiveDrawLayer.Interactive;
                    }
                    break;
                case GInteractiveChangeState.LeftDragEnd:
                    this._OnLocationChanged(e);
                    this.RepaintAllItems = true;
                    break;
                case GInteractiveChangeState.MouseLeave:
                    e.RequiredCursorType = SysCursorType.Default;
                    this.RepaintToLayers = GInteractiveDrawLayer.Standard;
                    break;
            }
        }
        private void _OnLocationChanging(GInteractiveChangeStateArgs e)
        {
            if (this.LocationChanging != null)
            {
                this.LocationChanging(this, EventArgs.Empty);
            }
        }
        private void _OnLocationChanged(GInteractiveChangeStateArgs e)
        {
            if (this.LocationChanged != null)
            {
                this.LocationChanged(this, EventArgs.Empty);
            }
        }
        #endregion
        #region Draw
        protected override void Draw(GInteractiveDrawArgs e)
        {
            if (this.Bounds.Width <= 0 || this.Bounds.Height <= 0) return;

            if (e.DrawLayer == GInteractiveDrawLayer.Standard)
            {
                if (this.CurrentState == GInteractiveState.LeftDrag)
                    this._Draw(e, this._GetCurrentShadowBrush);
                else if (this.CurrentState == GInteractiveState.Mouse)
                    this._Draw(e, this._GetCurrentMouseBrush);
                else
                    this._Draw(e, this._GetCurrentStandardBrush);
            }
            if (e.DrawLayer == GInteractiveDrawLayer.Interactive && this.CurrentState == GInteractiveState.LeftDrag)
            {
                this._Draw(e, this._GetCurrentInteractiveBrush);
            }
        }
        private void _Draw(GInteractiveDrawArgs e, Func<Brush> creator)
        {
            using (Brush b = creator())
            {
                e.Graphics.FillRectangle(b, this.Bounds);
            }
        }
        private Brush _GetCurrentStandardBrush()
        {
            float angle = (this._IsHorizontal ? -90f : 180f);
            System.Drawing.Drawing2D.LinearGradientBrush b = new System.Drawing.Drawing2D.LinearGradientBrush(this.Bounds, Color.DimGray, Color.LightGray, angle);
            b.SetSigmaBellShape(0.4f);
            return b;
        }
        private Brush _GetCurrentMouseBrush()
        {
            float angle = (this._IsHorizontal ? -90f : 180f) + 180f;
            System.Drawing.Drawing2D.LinearGradientBrush b = new System.Drawing.Drawing2D.LinearGradientBrush(this.Bounds, Color.Gray, Color.White, angle);
            b.SetSigmaBellShape(0.5f, 0.25f);
            return b;
        }
        private Brush _GetCurrentInteractiveBrush()
        {
            float angle = (this._IsHorizontal ? -90f : 180f);
            System.Drawing.Drawing2D.LinearGradientBrush b = new System.Drawing.Drawing2D.LinearGradientBrush(this.Bounds, Color.Gray, Color.LightYellow, angle);
            b.SetSigmaBellShape(0.5f);
            return b;
        }
        private Brush _GetCurrentShadowBrush()
        {
            float angle = (this._IsHorizontal ? -90f : 180f);
            System.Drawing.Drawing2D.LinearGradientBrush b = new System.Drawing.Drawing2D.LinearGradientBrush(this.Bounds, Color.Gray, Color.White, angle);
            b.SetSigmaBellShape(0.5f, 0.25f);
            return b;
        }
        #endregion
    }
}
